﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace LibEBML.Common
{
    public class EBMLDocument : IDisposable
    {

        public EBMLContainer Header { get; protected set; }
        public IList<EBMLContainer> Segments { get; protected set; }
        public ElementFactory Factory { get; private set; }


        public EBMLDocument(Stream Source)
        {
            var f = new ElementFactory(this);
            Initialize(Source, f);
        }
        public EBMLDocument(Stream Source, ElementFactory factory)
        {
            Initialize(Source, factory);
        }
        protected virtual void Initialize(Stream Source, ElementFactory factory)
        {
            this.Factory = factory;
            BaseSource = Source;
            GetHeader();
            GetSegments();
        }

        protected Stream BaseSource;

        protected void GetHeader()
        {
            Header = Factory.GetElement(BaseSource, 0) as EBMLContainer;
            //Header = new EBMLHeader(BaseSource, 0, Size, v1.Length + v2.Length);
        }
        protected void GetSegments()
        {
            Segments = new List<EBMLContainer>();
            var pos = Header.Length;
            while (pos < BaseSource.Length)
            {
                EBMLContainer seg = Factory.GetElement(BaseSource, pos) as EBMLContainer;
                Segments.Add(seg);
                pos += seg.Length;
            }
        }

        public virtual void Dispose()
        {
            if (Header != null)
            {
                Header.Dispose();
            }
            if (Segments != null)
            {
                foreach (var item in Segments)
                {
                    item.Dispose();
                }
            }
            BaseSource = null;
        }

    }
    public abstract class Element : IDisposable
    {
        protected Element(EBMLDocument doc, string Name, Stream Source, long Pos, long ID, long Size, long DataPos)
        {
            if (!Source.CanSeek)
            {
                throw new NotSupportedException("不受支持的数据源");
            }
            Document = doc;
            this.Name = Name;
            BaseSource = Source;
            Position = Pos;
            BaseSource.Position = DataPos;
            this.ID = ID;
            this.Size = Size;
            DataPosition = DataPos;
            Length = Size + (DataPos - Pos);
        }
        public virtual string Name { get; protected set; }
        public virtual long ID { get; protected set; }
        public virtual long Size { get; protected set; }
        public virtual Stream BaseSource { get; protected set; }
        public virtual long Position { get; protected set; }
        public virtual long DataPosition { get; protected set; }
        public virtual long Length { get; protected set; }
        public virtual EBMLDocument Document { get; protected set; }
        public virtual void Dispose()
        {
            Document = null;
            BaseSource = null;
        }
    }
    public class ElementFactory
    {
        internal ElementFactory(EBMLDocument doc)
        {
            document = doc;

            #region "Containers"

            _containers[0x001a45dfa3] = "EBML";
            _containers[0x00c3] = "CRC32";

            _containers[0x0018538067] = "Segment";

            _containers[0x00114d9b74] = "SeekHead";
            _containers[0x004dbb] = "Seek";

            _containers[0x001549a966] = "Info";

            _containers[0x001f43b675] = "Cluster";
            _containers[0x00a0] = "BlockGroup";
            _containers[0x0075a1] = "BlockAdditions";
            _containers[0x00a6] = "BlockMore";
            _containers[0x008e] = "Slices";
            _containers[0x00e8] = "TimeSlice";

            _containers[0x001654ae6b] = "Tracks";
            _containers[0x00ae] = "TrackEntry";
            _containers[0x00e0] = "Video";
            _containers[0x00e1] = "Audio";
            _containers[0x006d80] = "ContentEncodings";
            _containers[0x006240] = "ContentEncoding";
            _containers[0x005034] = "ContentCompression";
            _containers[0x005035] = "ContentEncryption";

            _containers[0x001c53bb6b] = "Cues";
            _containers[0x00bb] = "CuePoint";
            _containers[0x00b7] = "CueTrackPositions";
            _containers[0x00db] = "CueReference";

            _containers[0x001941a469] = "Attachments";
            _containers[0x0061a7] = "AttachedFile";

            _containers[0x001043a770] = "Chapters";
            _containers[0x0045b9] = "EditionEntry";
            _containers[0x00b6] = "ChapterAtom";
            _containers[0x008f] = "ChapterTrack";
            _containers[0x0080] = "ChapterDisplay";

            _containers[0x001254c367] = "Tags";
            _containers[0x007373] = "Tag";
            _containers[0x0063c0] = "Targets";
            _containers[0x0067c8] = "SimpleTag";
            #endregion

            #region "Ints"
            _ints[0x00fb] = "ReferenceBlock";
            _ints[0x00fd] = "ReferenceVirtual";
            #endregion

            #region "UInts"
            _uints[0x004286] = "EBMLVersion";
            _uints[0x0042f7] = "EBMLReadVersion";
            _uints[0x0042f2] = "EBMLMaxIDLength";
            _uints[0x0042f3] = "EBMLMaxSizeLength";
            _uints[0x004287] = "DocTypeVersion";
            _uints[0x004285] = "DocTypeReadVersion";


            _uints[0x0053ac] = "SeekPosition";

            _uints[0x002ad7b1] = "TimecodeScale";

            _uints[0x00e7] = "Timecode";
            _uints[0x00a7] = "Position";
            _uints[0x00ab] = "PrevSize";
            _uints[0x00ee] = "BlockAddID";
            _uints[0x009b] = "BlockDuration";
            _uints[0x00fa] = "ReferencePriority";
            _uints[0x00cc] = "LaceNumber";
            _uints[0x00cd] = "FrameNumber";
            _uints[0x00cb] = "BlockAdditionID";
            _uints[0x00ce] = "Delay";
            _uints[0x00cf] = "Duration";

            _uints[0x00d7] = "TrackNumber";
            _uints[0x0073c5] = "TrackUID";
            _uints[0x0083] = "TrackType";
            _uints[0x00b9] = "FlagEnabled";
            _uints[0x0088] = "FlagDefault";
            _uints[0x55aa] = "FlagForced";
            _uints[0x009c] = "FlagLacing";
            _uints[0x006de7] = "MinCache";
            _uints[0x006df8] = "MaxCache";
            _uints[0x0023e383] = "DefaultDuration";
            _uints[0x00aa] = "CodecDecodeAll";
            _uints[0x006fab] = "TrackOverlay";
            _uints[0x009a] = "FlagInterlaced";
            _uints[0x0053b8] = "StereoMode";
            _uints[0x00b0] = "PixelWidth";
            _uints[0x00ba] = "PixelHeight";
            _uints[0x0054b0] = "DisplayWidth";
            _uints[0x0054ba] = "DisplayHeight";
            _uints[0x0054b2] = "DisplayUnit";
            _uints[0x0054b3] = "AspectRatioType";
            _uints[0x009f] = "Channels";
            _uints[0x006264] = "BitDepth";
            _uints[0x005031] = "ContentEncodingOrder";
            _uints[0x005032] = "ContentEncodingScope";
            _uints[0x005033] = "ContentEncodingType";
            _uints[0x004254] = "ContentCompAlgo";
            _uints[0x0047e1] = "ContentEncAlgo";
            _uints[0x0047e5] = "ContentSigAlgo";

            _uints[0x00b3] = "CueTime";
            _uints[0x00f7] = "CueTrack";
            _uints[0x00f1] = "CueClusterPosition";
            _uints[0x005378] = "CueBlockNumber";
            _uints[0x00ea] = "CueCodecState";
            _uints[0x0096] = "CueRefTime";
            _uints[0x0097] = "CueRefCluster";
            _uints[0x00535f] = "CueRefNumber";
            _uints[0x00eb] = "CueRefCodecState";

            _uints[0x0046ae] = "FileUID";
            _uints[0x0073c4] = "ChapterUID";
            _uints[0x0091] = "ChapterTimeStart";
            _uints[0x0092] = "ChapterTimeEnd";
            _uints[0x0098] = "ChapterFlagHidden";
            _uints[0x004598] = "ChapterFlagEnabled";
            _uints[0x0089] = "ChapterTrackNumber";

            _uints[0x0063c5] = "TrackUID";
            _uints[0x0063c4] = "ChapterUID";
            _uints[0x0063c6] = "AttachmentUID";

            #endregion

            #region "Binaries"

            _binaries[0x0042fe] = "CRC32Value";

            _binaries[0x0053ab] = "SeekID";

            _binaries[0x0073a4] = "SegmentUID";
            _binaries[0x003cb923] = "PrevUID";
            _binaries[0x003eb923] = "NextUID";

            _binaries[0x00a1] = "Block";
            _binaries[0x00a2] = "BlockVirtual";
            _binaries[0xa5] = "BlockAdditional";
            _binaries[0x00a4] = "CodecState";
            _binaries[0x0063a2] = "CodecPrivate";
            _binaries[0x002eb524] = "ColourSpace";
            _binaries[0x007d7b] = "ChannelPositions";
            _binaries[0x004255] = "ContentCompSettings";
            _binaries[0x0047e2] = "ContentEncKeyID";
            _binaries[0x0047e3] = "ContentSignature";
            _binaries[0x0047e4] = "ContentSigKeyID";
            _binaries[0x00465c] = "FileData";
            _binaries[0x004485] = "TagBinary";
            _binaries[0x00a3] = "SimpleBlock";
            //_binaries[0x00a1] = "Block";
            //_binaries[0x00] = "";
            //_binaries[0x00] = "";
            //_binaries[0x00] = "";
            //_binaries[0x00] = "";
            //_binaries[0x00] = "";
            //_binaries[0x00] = "";
            //_binaries[0x00] = "";

            #endregion

            #region "Strings"
            _strings[0x004282] = "DocType";

            _strings[0x007384] = "SegmentFilename";
            _strings[0x003c83ab] = "PrevFilename";
            _strings[0x003e83bb] = "NextFilename";
            _strings[0x007ba9] = "Title";
            _strings[0x004d80] = "MuxingApp";
            _strings[0x005741] = "WritingApp";

            _strings[0x00536e] = "Name";
            _strings[0x0022b59c] = "Language";
            _strings[0x0086] = "CodecID";
            _strings[0x00258688] = "CodecName";
            _strings[0x003a9697] = "CodecSettings";
            _strings[0x003b4040] = "CodecInfoURL";
            _strings[0x0026b240] = "CodecDownloadURL";

            _strings[0x00467e] = "FileDescription";
            _strings[0x00466e] = "FileName";
            _strings[0x004660] = "FileMimeType";

            _strings[0x0085] = "ChapString";
            _strings[0x00437c] = "ChapLanguage";
            _strings[0x00437e] = "ChapCountry";
            _strings[0x0045a3] = "TagName";
            _strings[0x004487] = "TagString";

            #endregion

            #region "Floats"
            _floats[0x004489] = "Duration";
            _floats[0x0023314f] = "TrackTimecodeScale";
            _floats[0x002fb523] = "GammaValue";
            _floats[0x00b5] = "SamplingFrequency";
            _floats[0x0078b5] = "OutputSamplingFrequency";
            #endregion
            #region "Dates"
            _dates[0x004461] = "DateUTC";
            #endregion
        }
        EBMLDocument document;
        IDictionary<long, string> _containers = new Dictionary<long, string>();
        IDictionary<long, string> _ints = new Dictionary<long, string>();
        IDictionary<long, string> _uints = new Dictionary<long, string>();
        IDictionary<long, string> _floats = new Dictionary<long, string>();
        IDictionary<long, string> _strings = new Dictionary<long, string>();
        IDictionary<long, string> _binaries = new Dictionary<long, string>();
        IDictionary<long, string> _dates = new Dictionary<long, string>();
        //IList<long> voids = new List<long>();
        internal Element GetElement(Stream Source, long Pos)
        {
            Element m = null;
            Source.Position = Pos;
            byte[] b = new byte[1024];
            int len = Source.Read(b, 0, b.Length);
            var v1 = GetVintID(b, 0, len);
            var v2 = GetVintSize(b, v1.Length, len);
            var ID = ConvertVintToLong(v1);
            var Size = ConvertVintToLong(v2);
            if (_containers.ContainsKey(ID))
            {
                m = new EBMLContainer(document, _containers[ID], Source, Pos, ID, Size, Pos + v1.Length + v2.Length);
            }
            else if (_ints.ContainsKey(ID))
            {
                m = new EBMLType<short>(document, _ints[ID], Source, Pos, ID, Size, Pos + v1.Length + v2.Length);
            }
            else if (_uints.ContainsKey(ID))
            {
                m = new EBMLType<ulong>(document, _uints[ID], Source, Pos, ID, Size, Pos + v1.Length + v2.Length);
            }
            else if (_floats.ContainsKey(ID))
            {
                m = new EBMLType<decimal>(document, _floats[ID], Source, Pos, ID, Size, Pos + v1.Length + v2.Length);
            }
            else if (_dates.ContainsKey(ID))
            {
                m = new EBMLType<DateTime>(document, _dates[ID], Source, Pos, ID, Size, Pos + v1.Length + v2.Length);
            }
            else if (_binaries.ContainsKey(ID))
            {
                m = new EBMLBinary(document, _binaries[ID], Source, Pos, ID, Size, Pos + v1.Length + v2.Length);
            }
            else if (_strings.ContainsKey(ID))
            {
                m = new EBMLString(document, _strings[ID], Source, Pos, ID, Size, Pos + v1.Length + v2.Length);
            }
            else
            {
                //voids.Add(ID);
                m = new EBMLBinary(document, "Void", Source, Pos, ID, Size, Pos + v1.Length + v2.Length);
            }
            // Console.WriteLine(m.Name);
            return m;
        }
        public static byte[] GetVintID(byte[] source, int Offset, int Length)
        {
            int Width = 0;
            byte Align = 0;
            byte[] Tail = new byte[1];
            for (int i = Offset; i < Length; i++)
            {
                if (source[i] == 0)
                {
                    Width += 8;
                }
                else
                {
                    byte mask = 128;
                    int lt = 0;
                    while (mask > 0)
                    {
                        if ((source[i] & mask) > 0)
                        {
                            Align = source[i];
                            Width += lt;
                            break;
                        }
                        else
                        {
                            mask /= 2;
                            lt++;
                        }
                    }
                    Tail = new byte[Width + 1];
                    Tail[0] = Align;
                    if (Width > 0)
                    {
                        Buffer.BlockCopy(source, i + 1, Tail, 1, Width);
                    }
                    break;
                }
            }
            return Tail;
        }

        public static byte[] GetVintSize(byte[] source, int Offset, int Length)
        {
            int Width = 0;
            byte Align = 0;
            byte[] Tail = new byte[1];
            for (int i = Offset; i < Length; i++)
            {
                if (source[i] == 0)
                {
                    Width += 8;
                }
                else
                {
                    byte mask = 128;
                    int lt = 0;
                    while (mask > 0)
                    {
                        if ((source[i] & mask) > 0)
                        {
                            Align = (byte)(source[i] - mask);
                            Width += lt;
                            break;
                        }
                        else
                        {
                            mask /= 2;
                            lt++;
                        }
                    }
                    Tail = new byte[Width + 1];
                    Tail[0] = Align;
                    if (Width > 0)
                    {
                        Buffer.BlockCopy(source, i + 1, Tail, 1, Width);
                    }
                    break;
                }
            }
            return Tail;
        }

        public static byte[] GetSignedSize(byte[] source, int Offset, int Length)
        {
            int Width = 0;
            byte Align = 0;
            byte[] Tail = new byte[1];
            for (int i = Offset; i < Length; i++)
            {
                if (source[i] == 0)
                {
                    Width += 8;
                }
                else
                {
                    byte mask = 128;
                    int lt = 0;
                    while (mask > 0)
                    {
                        if ((source[i] & mask) > 0)
                        {
                            Align = (byte)(source[i] - mask);
                            Width += lt;
                            break;
                        }
                        else
                        {
                            mask /= 2;
                            lt++;
                        }
                    }
                    Tail = new byte[Width + 1];
                    Tail[0] = Align;
                    if (Width > 0)
                    {
                        Buffer.BlockCopy(source, i + 1, Tail, 1, Width);
                    }
                    break;
                }
            }
            return Tail;
        }

        public static long ConvertVintToLong(byte[] Tail)
        {
            long result = 0;
            for (int i = 0; i < Tail.Length; i++)
            {
                result = result * 256 + Tail[i];
            }
            return result;
        }
        public static long ConvertVintToLongSign(byte[] Tail)
        {
            long result = 0;
            for (int i = 0; i < Tail.Length; i++)
            {
                result = result * 256 + Tail[i];
            }
            long x = 0;
            switch (Tail.Length)
            {
                case 1:
                    result -= 63;
                    break;
                case 2:
                    result -= 8191;
                    break;
                case 3:
                    result -= 1048575L;
                    break;
                case 4:
                    result -= 134217727L;
                    break;
            }
            return result;
        }

        public static T GetValue<T>(byte[] data) where T : struct
        {
            T value = default(T);
            if (value is long)
            {
                byte[] b = new byte[8];
                Buffer.BlockCopy(data.Reverse().ToArray(), 0, b, 0, data.Length);
                object obj = BitConverter.ToInt64(b, 0);
                value = (T)obj;
            }
            else if (value is int)
            {
                byte[] b = new byte[4];
                Buffer.BlockCopy(data.Reverse().ToArray(), 0, b, 0, data.Length);
                object obj = BitConverter.ToInt32(b, 0);
                value = (T)obj;
            }
            else if (value is short)
            {
                byte[] b = new byte[2];
                Buffer.BlockCopy(data.Reverse().ToArray(), 0, b, 0, data.Length);
                object obj = BitConverter.ToInt16(b, 0);
                value = (T)obj;
            }
            else if (value is DateTime)
            {
                byte[] b = new byte[8];
                Buffer.BlockCopy(data.Reverse().ToArray(), 0, b, 0, data.Length);
                var tick = BitConverter.ToInt64(b, 0);
                DateTime dt = new DateTime(2001, 01, 01, 00, 00, 00, DateTimeKind.Utc);
                object obj = dt.AddTicks(tick);
                value = (T)obj;
            }
            else if (value is ulong)
            {
                byte[] b = new byte[8];
                Buffer.BlockCopy(data.Reverse().ToArray(), 0, b, 0, data.Length);
                object obj = BitConverter.ToUInt64(b, 0);
                value = (T)obj;
            }
            else if (value is decimal)
            {
                object obj = decimal.Zero;
                if (data.Length == 4)
                {
                    obj = new decimal(BitConverter.ToSingle(data.Reverse().ToArray(), 0));
                }
                else if (data.Length == 8)
                {
                    obj = new decimal(BitConverter.ToDouble(data.Reverse().ToArray(), 0));
                }
                else if (data.Length == 10)
                {

                }
                value = (T)obj;
            }
            return value;
        }
    }

    public class EBMLContainer : Element, IEnumerable<Element>
    {
        protected internal EBMLContainer(EBMLDocument document, string Name, Stream Source, long Pos, long ID, long Size, long DataPos)
            : base(document, Name, Source, Pos, ID, Size, DataPos)
        {
            //GetChildrens();
        }
        protected virtual void GetChildrens()
        {
            m_items = new List<Element>();
            var pos = DataPosition;
            while (pos < DataPosition + Size)
            {
                Element seg = Document.Factory.GetElement(BaseSource, pos);
                Items.Add(seg);
                pos += seg.Length;
            }
        }
        protected List<Element> m_items = null;
        public IList<Element> Items
        {
            get
            {
                if (m_items == null)
                {
                    GetChildrens();
                }
                return m_items;
            }
        }
        public IEnumerator<Element> GetEnumerator()
        {
            return Items.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Items.GetEnumerator();
        }
        public override string ToString()
        {
            return string.Format("{0} Contains:{1}", Name, Items.Count);
        }
        public IEnumerable<Element> this[long ID]
        {
            get
            {
                return Items.Where(m => m.ID == ID);
            }
        }
        public IEnumerable<Element> this[string Name]
        {
            get
            {
                return Items.Where(m => m.Name == Name);
            }
        }
    }

    public class EBMLType<T> : Element
        where T : struct
    {
        protected internal EBMLType(EBMLDocument document, string Name, Stream Source, long Pos, long ID, long Size, long DataPos)
            : base(document, Name, Source, Pos, ID, Size, DataPos)
        {

        }
        T m_Value;
        bool HasValue;
        public T Value
        {
            get
            {
                if (!HasValue)
                {
                    BaseSource.Position = DataPosition;
                    var buff = new byte[Size];
                    int l = BaseSource.Read(buff, 0, buff.Length);
                    m_Value = ElementFactory.GetValue<T>(buff);
                    HasValue = true;
                }
                return m_Value;
            }
        }
        public override string ToString()
        {
            return string.Format("{0}:={1} ", Name, Value);
        }
    }

    public class EBMLString : Element
    {
        protected internal EBMLString(EBMLDocument document, string Name, Stream Source, long Pos, long ID, long Size, long DataPos)
            : base(document, Name, Source, Pos, ID, Size, DataPos)
        {

        }
        bool HasValue;
        string m_Value;
        public string Value
        {
            get
            {
                if (!HasValue)
                {
                    BaseSource.Position = DataPosition;
                    var buff = new byte[Size];
                    int l = BaseSource.Read(buff, 0, buff.Length);
                    m_Value = System.Text.Encoding.UTF8.GetString(buff, 0, l);
                    HasValue = true;
                }
                return m_Value;
            }
        }
        public override string ToString()
        {
            return string.Format("{0}:={1} ", Name, Value);
        }
    }

    public class EBMLBinary : Element
    {
        protected internal EBMLBinary(EBMLDocument document, string Name, Stream Source, long Pos, long ID, long Size, long DataPos)
            : base(document, Name, Source, Pos, ID, Size, DataPos)
        {

        }
        public override string ToString()
        {
            return string.Format("{0}:={1} Binary Data ", Name, Size);
        }
    }
}
